#include "common.h"
#include "globals.h"
#include "loadfile.h"
#include "html.h"


#include "htmlform.c"

/* TMacro - information about a macro invocation.
 *
 * We allow simple macro invocations of the form "@NAME@",
 * where "NAME" is the name of a function (TFunc) in a table
 * defined below. Macro invocations may also supply an argument,
 * as with "@INCLUDE='file.ext'@"; the value inside the quoted
 * string will be stored in TMacro.Value.
 */
typedef struct  TMacro
    {
    const char* Name;
    size_t      NameLen;
    char*       Value;
    }           TMacro;

typedef void (*HTMLFUNC)(FILE* Output, TMacro* Macro);

typedef struct  TFunc
    {
    const char* Name;
    HTMLFUNC    Func;
    }           TFunc;


/* htmlf() - a printf()-style function that escapes XML characters.
 *
 * (But not those appearing in the format string...) Geez, what
 * a pain. Must've been an easier way I was too dumb to see.
 */
#define OP_EOS      (0)
#define OP_GETARG   (1)
#define OP_OUTINT   (2)
#define OP_OUTLONG  (3)
#define OP_OUTPTR   (4)
typedef struct  TMachine
    {
    int         State;
    char        FmtSpec[32];
    char*       Output;
    int         IntArg;
    const char* Input;
    int         DidDot;
    }           TMachine;

enum {STATE_START, STATE_START_FMT, STATE_PRE_ARG, STATE_ARG, STATE_DOT, STATE_SUFFIX};
static
int     NextState(FILE* Output, TMachine* Machine)
    {
    int     Type;
    char    Char;

    for(;;)
    switch(Machine->State)
        {
        case    STATE_START:
            Machine->Output = Machine->FmtSpec;
            Machine->DidDot = FALSE;
            Char    = *Machine->Input++;
            if(Char == '\0')
                return OP_EOS;
            else if(Char == '%')
                {
                *Machine->Output++  = Char;
                Machine->State      = STATE_START_FMT;
                }
            else    /* else, literal character -- just spit it out */
                fputc(Char, Output);
            break;
        case    STATE_START_FMT:    /* just saw '%' */
            /* skip flags */
            while(strchr("-+ #0", *Machine->Input))
                *Machine->Output++  = *Machine->Input++;
            /* fall through */
        case    STATE_PRE_ARG:
            Char    = *Machine->Input;
            if(Char == '*')
                {
                ++Machine->Input;
                Machine->State  = STATE_ARG;
                return OP_GETARG;
                }
            while(isdigit(Char))
                {
                *Machine->Output++  = Char;
                Char                = *++Machine->Input;
                }
            Machine->State  = Machine->DidDot ? STATE_SUFFIX : STATE_DOT;
            break;
        case    STATE_ARG:     /* caller just fetched an integer arg for us */
            sprintf(Machine->Output, "%d", Machine->IntArg);
            Machine->Output+= strlen(Machine->Output);
            Machine->State  = Machine->DidDot ? STATE_SUFFIX : STATE_DOT;
            break;
        case    STATE_DOT:      /* looking for optional '.' */
            Char    = *Machine->Input;
            if(Char == '.')
                {
                *Machine->Output++  = *Machine->Input++;
                }
            Machine->DidDot     = TRUE;
            Machine->State  = STATE_PRE_ARG;
            break;
        case    STATE_SUFFIX:
            Type    = OP_OUTINT;
            if(strchr("hlL", *Machine->Input))
                {
                if(*Machine->Input == 'l')
                    Type    = OP_OUTLONG;
                *Machine->Output++  = *Machine->Input++;
                }
            switch(*Machine->Input)
                {
                case    's' :
                case    'p' :
                    Type    = OP_OUTPTR;
                    break;
                case    'c' :
                case    'd' :
                case    'u' :
                    break;
                default:
                    assert(FALSE);
                }
            *Machine->Output++  = *Machine->Input++;
            *Machine->Output    = '\0';
            Machine->State      = STATE_START;
            return Type;
        default:
            assert(FALSE);
        }
    }

#define INT_SPEC    (1)
#define LONG_SPEC   (2)
#define PTR_SPEC    (3)

static void htmlf(FILE* Output, const char* Format, ...)
    {
    TMachine    Machine = {STATE_START, 0};
    int         Opcode;
    const char* Rover;
    char*       Buffer;
    va_list     ArgPtr;

    Buffer      = malloc(1024*8);

    assert(Buffer != NULL);
    assert(Format != NULL);
    va_start(ArgPtr, Format);
    Machine.Input   = Format;
    while((Opcode=NextState(Output, &Machine)) != OP_EOS)
        {
        if(Opcode == OP_EOS)
            break;
        else if(Opcode == OP_GETARG)
            Machine.IntArg  = va_arg(ArgPtr, int);
        else
            {
            if(Opcode == OP_OUTINT)
                sprintf(Buffer, Machine.FmtSpec, va_arg(ArgPtr, int));
            else if(Opcode == OP_OUTLONG)
                sprintf(Buffer, Machine.FmtSpec, va_arg(ArgPtr, long));
            else if(Opcode == OP_OUTPTR)
                sprintf(Buffer, Machine.FmtSpec, va_arg(ArgPtr, void*));
            for(Rover=Buffer; *Rover; ++Rover)
            switch(*Rover)
                {
                case    '&'     : fputs("&amp;", Output);   break;
                case    '<'     : fputs("&lt;", Output);    break;
                case    '>'     : fputs("&gt;", Output);    break;
                case    '\''    : fputs("&apos;", Output);  break;
                case    '\"'    : fputs("&quot;", Output);  break;
                default:
                    fputc(*Rover, Output);
                }
            }
        }
#if 0
    while(*Format)
        {
        switch(*Format)
            {
            case    '%' :
                Format = GetSpec(Format, Spec, &Type);
                switch(Type)
                    {
                    case    INT_SPEC :
                        IntArg  = va_arg(ArgPtr, int);
                        sprintf(Buffer, Spec, IntArg);
                        break;
                    case    LONG_SPEC :
                        LongArg = va_arg(ArgPtr, long);
                        sprintf(Buffer, Spec, LongArg);
                        break;
                    case    PTR_SPEC  :
                        PtrArg  = va_arg(ArgPtr, void*);
                        sprintf(Buffer, Spec, PtrArg);
                        break;
                    default:
                        assert(FALSE);
                    }
                break;
            default:
                fputc(*Format, Output);
            }
        ++Format;
        }
#endif
    va_end(ArgPtr);

    free(Buffer);
    }

static
void    HtmlInclude(FILE* Output, TMacro* Macro)
    {
    char*   File;

    if(Macro->Value)
        {
        File    = LoadFile(Macro->Value);
        fputs(File, Output);
        free(File);
        }
    else
        fprintf(stderr, "@INCLUDE@ macro requires argument naming file to include!\n");
    }

static
void    HtmlTitle(FILE* Output, TMacro* Macro)
    {
    ARGUNUSED(Macro);
    
    fprintf(Output, "blacc: '%s'", Globals.InputFile);
    }

static
void    HtmlOperator(FILE* Output, TToken Token)
    {
    const char* Rover = Token.Text;

Dump("HtmlOperator(%d)\n", Token.TextLen);
    htmlf(Output, "<span class='blc_op'>");
    while(Rover < Token.Text + Token.TextLen)
        {
        if(*Rover == 'X')
            htmlf(Output, "<span class='blc_X'>X</span>");
        else
            htmlf(Output, "%c", *Rover);
        ++Rover;
Dump("... \n");
        }
    htmlf(Output, "</span>");
Dump("HtmlOperator() returns\n");
    }
static
void    HtmlIllegalToken(FILE* Output, TToken Token)
    {
    const char* Rover = Token.Text;

Dump("HtmlIllegalToken(%d)\n", Token.TextLen);
    htmlf(Output, "<span class='blc_illegal'>");
    while(Rover < Token.Text + Token.TextLen)
        {
            htmlf(Output, "%c", *Rover);
        ++Rover;
        }
    htmlf(Output, "</span>");
Dump("HtmlIllegalToken() returns\n");
    }
    
/* HtmlProgram() - emit any text that follows the second '%%'
 */
static
void    HtmlProgram(FILE* Output, TMacro* Macro)
    {
    TToken* Rover = InputGetTokens();
    
    ARGUNUSED(Macro);

    while(Rover->Type != TK_EOF)
        {
        if(Rover->Type == TK_PROGRAM)
            {
            htmlf(Output, "%.*s",
                Rover->TextLen, Rover->Text);
            break;
            }
        ++Rover;
        }
    }
    
static
void    HtmlGrammar(FILE* Output, TMacro* Macro)
    {
    TToken* Rover = InputGetTokens();
    
    ARGUNUSED(Macro);

    while(Rover->Type != TK_EOF && Rover->Type != TK_PROGRAM)
        {
    switch(Rover->Type)
        {
        case    TK_IDENT:
            if(SymbolFind(*Rover))
                htmlf(Output, "<span class='blc_sym'>%.*s</span>",
                Rover->TextLen, Rover->Text,
                Rover->TextLen, Rover->Text);
            else
                htmlf(Output, "<span>%.*s</span>",
                Rover->TextLen, Rover->Text,
                Rover->TextLen, Rover->Text);
            break;
        case    TK_NEWLINE:
            htmlf(Output, "<br/>\n");
            break;
        case    TK_COMMENT:
            htmlf(Output, "<span class='blc_cmnt'>%.*s</span>", Rover->TextLen, Rover->Text);
            break;
        case    TK_OPERATOR:
            HtmlOperator(Output, *Rover);
            break;
        case    TK_ILLEGAL:
        case    TK_BADQUOTE:
            HtmlIllegalToken(Output, *Rover);
            break;
        case    TK_SECTION:
            htmlf(Output, "<span class='blc_section'>%.*s</span>", Rover->TextLen, Rover->Text);
            break;
        case    TK_LEFT:
        case    TK_RIGHT:
        case    TK_NONASSOC:
        case    TK_TEST:
        case    TK_START:
        case    TK_TOKEN:
            htmlf(Output, "<span class='blc_kw'>%.*s</span>", Rover->TextLen, Rover->Text);
            break;
        default:
            fprintf(Output, "%.*s", Rover->TextLen, Rover->Text);
        }
        ++Rover;
        }
    }


static
TFunc   Funcs[] =
    {
    { "TITLE",          HtmlTitle           },
    { "GRAMMAR",        HtmlGrammar         },
    { "INCLUDE",        HtmlInclude         },
    { "PROGRAM",        HtmlProgram         },

// sentinel
    { NULL, NULL }
    };

static
int         IsQuote(int Char) { return (Char == '\'' || Char == '\"') ? Char : 0; }
static
//const char* IsMacro(const char* Rover)
const char* IsMacro(const char* Rover, TMacro* Macro)
    {
    int         QChar, Char;
    const char* Result  = NULL;
    const char* Value   = NULL;
    
    assert(*Rover == '@');
    ++Rover;
    /* if it could be start of a macro */
    if(isalpha(*Rover))
        {
        Macro->Name = Rover;
        while(isalpha(*Rover))  /* skip possible macro name */
            ++Rover;
        /* if it still could be a valid macro */
        if(*Rover == '@' || *Rover == '=')
            {
            /* now we know the name length, if it turns out to be a macro */
            Macro->NameLen  = Rover - Macro->Name;

            /* if macro name may have a value associated with it */
            if(*Rover == '=' && (QChar = IsQuote(Rover[1])) != 0)
                {
                Rover  += 2;        /* skip '=' and quotation mark */
                Value   = Rover;

                /* a doubled quote is an escape for a single quote */
                while((Char =*Rover++) != '\0' && (Char != QChar || *Rover++ == QChar))
                    ;
                if(Char)        /* if we found the matching end quote */
                    --Rover;    /* we overshot, checking for a doubled quote */
                if(*Rover == '@')
                    {
                    char*   Output;
                    Macro->Value    = Output = (char*)malloc(Rover-Value);
                    assert(Output != NULL);
                    for(;*Value != '@'; ++Value)
                        if(Value[0] == QChar && Value[1] == QChar)
                            *Output++   = *Value++;
                        else if(Value[0] == QChar)
                            {
                            *Output = '\0';
                            assert(Value[1] == '@');
                            }
                        else
                            *Output++   = *Value;
                    Result  = Rover;
                    }
                }
            else if(*Rover == '@')
                Result  = Rover;
            }
        }

    return Result;
    }

/* Html() - output HTML that provides GUI for this grammar.
 *
 * We generate the output based on an internal template or an
 * external file specified with the "-s" command-line option.
 * The resulting template must contain @COMMAND@ items in it;
 * these get replaced by particular generated code fragments.
 * For example, @GRAMMAR@ contains HTML for the original
 * grammar.
 */
typedef struct  TInput
    {
    char*       InStr;
    const char* Rover;
    struct TInput* Prev;
    }           TInput;
void    Html(FILE* Output)
    {
    char*       StyleFile;
    const char* Rover   = HtmlTemplate;
    const char* Skip;
    const char* Start;
    int         iFunc;
    TInput*     Input;
    TMacro      Macro   = {0};

    Input   = NEW(TInput);
    assert(Input != NULL);

    Dump("Html() begins\n");
    Rover   = Input->InStr    = HtmlTemplate;
    if(Globals.StyleFile)
        Rover = Input->InStr = StyleFile = LoadFile(Globals.StyleFile);
    while(*Rover)
        if(*Rover == '@' && (Skip=IsMacro(Rover, &Macro)) != NULL)
            {
Dump("Found Macro '%.*s'\n", Macro.NameLen, Macro.Name);
            for(iFunc=0; Funcs[iFunc].Name; ++iFunc)
                if(!strncmp(Funcs[iFunc].Name, Macro.Name, Macro.NameLen))
                    {
                    Funcs[iFunc].Func(Output, &Macro);
                    break;
                    }
            if(!Funcs[iFunc].Name)
                {
                fprintf(stderr, "Warning: unknown macro name ('%.*s') in HTML template.\n",
                            Macro.NameLen, Macro.Name);
                }
            if(Macro.Value)
                {
                free(Macro.Value);
                Macro.Value = NULL;
                }
            Rover = Skip + 1;
            }
        /* we eat leading white space on line preceding a macro */
        else if(isspace(*Rover))
            {
            Start   = Rover;
            while(isspace(*Start))
                ++Start;
            if(*Start == '@' && IsMacro(Start, &Macro))
                Rover   = Start;
            else
                fputc(*Rover++, Output);
            }
        else
            fputc(*Rover++, Output);
    if(Globals.StyleFile)
        free(Input->InStr);
    free(Input);

    Dump("Html() returns\n");
    }


